#include "guiterm.hpp"
#include <QKeyEvent>
#include <string.h>
#include <QTime>
#include <sys/time.h>
extern "C" {
	#include "putty.h"
	#include "terminal.h"
	#include "charset.h"
	#include "ldisc.h"
}

extern "C" {
	// Interfaces used to control the terminal
	Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, void *frontend);
	void term_free(Terminal *term);
	void term_size(Terminal *term, int newrows, int newcols, int newsavelines);
	void term_paint(Terminal *term, Context ctx, int left, int top, int right, int bottom, int immediately);
	void term_scroll(Terminal *term, int rel, int where);
	void term_pwron(Terminal *term, int clear);
	void term_update(Terminal *term);
	int term_data(Terminal *term, int is_stderr, const char *data, int len);

	// Interface the terminal uses to draw and interact
	void ldisc_send(void *handle, char *buf, int len, int interactive);
	Context get_ctx(void *frontend);
	void free_ctx(Context ctx);
	void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr);
	void do_text(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr);
	void set_sbar(void *frontend, int total, int start, int page);
	void timer_change_notify(long next);
	void do_beep(void *frontend, int mode);
	unsigned long getticks(void);
}

unsigned long getticks(void)
{
	QTime now;
	return 60000*now.minute() + 1000*now.second() + now.msec() + tickcount_offset;
	
    /*struct timeval tv;
    gettimeofday(&tv, NULL);
    // We want to use milliseconds rather than microseconds,
    // because we need a decent number of them to fit into a 32-bit
    // word so it can be used for keepalives.
    return tv.tv_sec * 1000 + tv.tv_usec / 1000 + tickcount_offset;*/
}

void ldisc_send(void *handle, char *buf, int len, int interactive)
{
	if(!len)
		return;
	
	ldisc_tag *ldisc = (ldisc_tag*)handle;
	Terminal *term = (Terminal*)ldisc->term;
	TerminalEmulator *widget = (TerminalEmulator*)term->frontend;
	
	RemoteJob *job = widget->getOwner();
	if(job) {
		string str(buf, len);
		job->sendData(buf, len);
	}
}

Context get_ctx(void *frontend)
{
	return frontend;
}

void free_ctx(Context ctx)
{
}

void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr)
{
	TerminalEmulator *emulator = (TerminalEmulator*)ctx;
	emulator->do_cursor(x, y, text, len, attr, lattr);
}

void do_text(Context ctx, int x, int y, wchar_t *text, int len, unsigned long attr, int lattr)
{
	TerminalEmulator *emulator = (TerminalEmulator*)ctx;
	emulator->do_text(x, y, text, len, attr, lattr);
}

void set_sbar(void *frontend, int total, int start, int page)
{
	TerminalEmulator *emulator = (TerminalEmulator*)frontend;
	emulator->setScrollbar(total, start, page);
}

void timer_change_notify(long next)
{
	frontend->timerChangeNotify(next);
}

void do_beep(void *frontend, int mode)
{
	QApplication::beep();
}

Config *getTermConfig(int width, int height)
{
	Config *ret = new Config;
	
	ret->bksp_is_delete = 0;
	ret->rxvt_homeend = 0;
	ret->funky_type = FUNKY_LINUX;      // function key type
	ret->no_applic_c = 0;               // totally disable app cursor keys
	ret->no_applic_k = 0;               // totally disable app keypad
	ret->no_mouse_rep = 0;              // totally disable mouse reporting
	ret->no_remote_resize = 1;          // disable remote resizing
	ret->no_alt_screen = 0;             // disable alternate screen
	ret->no_remote_wintitle = 0;        // disable remote retitling
	ret->no_dbackspace = 0;             // disable destructive backspace
	ret->no_remote_charset = 0;         // disable remote charset config
	ret->remote_qtitle_action = 1;      // remote win title query action
	ret->app_cursor = 0;
	ret->app_keypad = 1;
	ret->nethack_keypad = 0;
	ret->telnet_keyboard = 0;
	ret->telnet_newline = 0;
	ret->localecho = 0;
	ret->localedit = 0;
	ret->scroll_on_key = 0;
	ret->scroll_on_disp = 0;
	ret->erase_to_scrollback = 0;
	strcpy(ret->wintitle, "Terminal Emulator");           // initial window title
	// Terminal options
	ret->savelines = 500;
	ret->dec_om = 0;
	ret->wrap_mode = 1;
	ret->lfhascr = 1;     // Whether a \n implies a \r
	ret->cursor_type = 0;            // 0=block 1=underline 2=vertical
	ret->blink_cur = 0;
	ret->beep = 0;
	ret->beep_ind = 0;
	ret->bellovl = 0;                    // bell overload protection active?
	ret->bellovl_n = 4;                  // number of bells to cause overload
	ret->bellovl_t = 2;                  // time interval for overload (seconds)
	ret->bellovl_s = 2;                  // period of silence to re-enable bell (s)
	ret->scrollbar = 0;
	ret->scrollbar_in_fullscreen = 0;
	ret->bce = 1;
	ret->blinktext = 0;
	ret->width = width;
	ret->height = height;
	ret->logtype = 0;
	ret->logflush = 0;
	strcpy(ret->answerback, "__guishell__");
	strcpy(ret->printer, "");
	ret->arabicshaping = 1; // Means *disable* arabic shaping
	ret->bidi = 1; // Means *disable* bidirectional support
	// Colour options
	ret->ansi_colour = 1;
	ret->xterm_256_colour = 0;
	ret->system_colour = 0;
	ret->try_palette = 0;
	ret->bold_colour = 0;
	for(int ii=0; ii<22; ii++) {
		ret->colours[ii][0] = 255;
		ret->colours[ii][1] = 0;
		ret->colours[ii][2] = 255;
	}
	// Selection options
	ret->mouse_is_xterm = 0;
	ret->rect_select = 0;
	ret->rawcnp = 0;
	ret->rtf_paste = 0;
	ret->mouse_override = 0;
	memset(ret->wordness, 0, sizeof(ret->wordness));
	// translations
	ret->vtmode = VT_OEMONLY;
	memset(ret->line_codepage, 0, sizeof(ret->line_codepage));
	ret->cjk_ambig_wide = 0;
	ret->utf8_override = 0;
	ret->xlat_capslockcyr = 0;
	return ret;
}

TerminalEmulator::TerminalEmulator(RemoteJob *owner, int width, int height)
	:CharacterGrid(width)
{
	this->owner = owner;
	this->height = height;
	
	abridged = true;
	abridgedHeight = 1;
	
	terminalConfig = getTermConfig(width,height);
	ucsdata = new unicode_data();
	init_ucs(ucsdata, terminalConfig->line_codepage, 1, CS_UTF8, terminalConfig->vtmode);
	terminal = term_init(terminalConfig, ucsdata, (void*)this);
	
	ldisc_tag *ldisc = new ldisc_tag;
	terminal->ldisc = (void*)ldisc;
	ldisc->term = (Terminal*)terminal;
	ldisc->back = (Backend*)NULL;
	ldisc->cfg = (Config*)terminalConfig;
	ldisc->backhandle = NULL;
	ldisc->frontend = NULL;
	ldisc->buf = NULL;
	ldisc->buflen = ldisc->bufsiz = 0;
	ldisc->quotenext = 0;
	
	TerminalCell emptyCell;
	for(int ii=0; ii<height; ii++) {
		cells.push_back(vector<TerminalCell>());
		for(int jj=0; jj<width; jj++)
			cells[ii].push_back(emptyCell);
	}
	
	term_size(terminal, height, width, config.scrollbackSize);
	term_pwron(terminal, 1);
	term_paint(terminal, (void*)this, 0, 0, width-1, height-1, 1);
}

TerminalEmulator::~TerminalEmulator()
{
	term_free(terminal);
}

void TerminalEmulator::finish()
{
	if(abridged)
		setAbridgedHeight(calculateAbridgedHeight());
}

int TerminalEmulator::calculateAbridgedHeight()
{
	for(int ii=height-1; ii>=0; ii--) {
		for(int jj=0; jj<width; jj++) {
			if(cells[ii][jj].ch != ' ')
				return ii+1;
		}
	}
	return 1;
}

void TerminalEmulator::do_cursor(int x, int y, wchar_t *text, int len, unsigned long attr, int lattr)
{
	cursorMoved(x, y);
	do_text(x, y, text, len, attr, lattr);
}

void TerminalEmulator::do_text(int x, int y, wchar_t *text, int len, unsigned long attr, int lattr)
{
	if(y<0 || y>=height) {
		trace<<"TerminalEmulator::do_text out of bounds (y="<<y<<", height="<<height<<")\n";
		return;
	}
	if(x<0 || x+len>width) {
		trace<<"TerminalEmulator::do_text out of bounds (x="<<x<<")\n";
		return;
	}
	
	int xpos = x;
	bool nonblank = false;
	bool changed = false;
	int xmin=0, xmax=0;
	for(int ii=0; ii<len; ii++)
	{
		wchar_t ch = text[ii];
		if(ch && ch!=' ')
			nonblank = true;
		
		TerminalCell cell;
			cell.ch = ch;
			cell.attr = attr;
			cell.lattr = lattr;
		if(cells[y][xpos] != cell) {
			cells[y][xpos] = cell;
			if(changed) {
				xmax = xpos;
			} else {
				xmin = xmax = xpos;
				changed = true;
			}
		}
		xpos++;
	}
	
	if(nonblank && y+1 > abridgedHeight && abridged)
		setAbridgedHeight(y+1);
	
	if(changed)
		rectDirtied(xmin, y, xmax-xmin+1, 1);
}

TerminalEmulator::TerminalCell::TerminalCell()
{
	ch=' ';
	attr=0;
	lattr=0;
}

bool TerminalEmulator::TerminalCell::operator!=(const TerminalCell &oth)
{
	return ch!=oth.ch || attr!=oth.attr;
}

int TerminalEmulator::receiveData(const char *data, int length)
{
	term_data(terminal, 0, data, length);
	return length;
}

void TerminalEmulator::setWidth(int width)
{
	int oldWidth = this->width;
	if(width == oldWidth) return;
	
	if(width > oldWidth)
	{
		size_t extraCols = width - oldWidth;
		TerminalCell emptyCell;
		for(int ii=0; ii<height; ii++) {
			for(size_t jj=0; jj<extraCols; jj++)
				cells[ii].push_back(emptyCell);
		}
	} else {
		for(int ii=0; ii<height; ii++)
			cells[ii].erase(cells[ii].begin()+width, cells[ii].end());
	}
	
	this->width = width;
	term_size(terminal, height, width, config.scrollbackSize);
	term_invalidate(terminal);
	owner->winch(width, height);
	
	rectDirtied(0, 0, width, height);
}

void TerminalEmulator::setHeight(int height)
{
	int oldHeight = this->height;
	if(height==oldHeight) return;
	this->height = height;
	
	if(height > oldHeight) {
		size_t cols = width;
		TerminalCell emptyCell;
		for(int ii=oldHeight; ii<height; ii++) {
			cells.push_back(vector<TerminalCell>());
			for(size_t jj=0; jj<cols; jj++)
				cells[ii].push_back(emptyCell);
		}
	} else {
		cells.erase(cells.begin()+height, cells.end());
	}
	
	term_size(terminal, height, width, config.scrollbackSize);
	term_invalidate(terminal);
	owner->winch(width, height);
	
	rectDirtied(0, oldHeight, width, height-oldHeight);
}

int TerminalEmulator::getChar(int col, int row) const
{
	if(row<0 || row>=height)
		return ' ';
	if(col<0 || col>=width)
		return ' ';
	const TerminalCell *cell = &cells[row][col];
	return cell->ch;
}

static QColor paletteLookup(int color, bool bold)
{
	/*
	 * (From putty.h)
	 * The definitive list of colour numbers stored in terminal
	 * attribute words is kept here. It is:
	 * 
	 *  - 0-7 are ANSI colours (KRGYBMCW).
	 *  - 8-15 are the bold versions of those colours.
	 *  - 16-255 are the remains of the xterm 256-colour mode (a
	 *    216-colour cube with R at most significant and B at least,
	 *    followed by a uniform series of grey shades running between
	 *    black and white but not including either on grounds of
	 *    redundancy).
	 *  - 256 is default foreground
	 *  - 257 is default bold foreground
	 *  - 258 is default background
	 *  - 259 is default bold background
	 *  - 260 is cursor foreground
	 *  - 261 is cursor background
	 */
	if(color<0)
		return QColor(0,0,0);
	else if(color<16)
	{
		if(bold && color<8)
			color += 8;
		return config.colors[color];
	}
	else if(color<256)
	{
		// TODO: Look up in the standard 256-color palette
		return QColor(255,0,255); //Magic pink
	}
	else if(color==256)
		return config.programForeground;
	else if(color==257)
		return config.programBoldForeground;
	else if(color==258)
		return config.programBackground;
	else if(color==259)
		return config.programBoldBackground;
	else
	{
		trace << "Invalid color: "<<color<<".\n";
		return QColor(255,0,255);
	}
}

QColor TerminalEmulator::getForeColor(int col, int row) const
{
	if(row<0 || row>=height ||
	   col<0 || col>=width)
		return config.programForeground;
	
	const TerminalCell *cell = &cells[row][col];
	int attr = cell->attr;
	int color = (attr>>ATTR_FGSHIFT) & ATTR_FGMASK;
	bool bold = attr & ATTR_BOLD;
	return paletteLookup(color, bold);
}

QColor TerminalEmulator::getBackColor(int col, int row) const
{
	if(row<0 || row>=height ||
	   col<0 || col>=width)
		return config.programBackground;
	
	const TerminalCell *cell = &cells[row][col];
	int attr = cell->attr;
	int color = (attr & ATTR_BGMASK) >> ATTR_BGSHIFT;
	bool bold = attr & ATTR_BOLD;
	return paletteLookup(color, bold);
}

int TerminalEmulator::heightForWidth(int width, bool abridged) const
{
	if(abridged)
		return abridgedHeight;
	else
		return height;
}

int TerminalEmulator::getTerminalHeight() const
{
	return height;
}

void TerminalEmulator::setScrollbar(int total, int start, int page)
{
	scrollbarTotal = total;
	scrollbarStart = start;
	
	int scrollMax = total-page;
	if(scrollMax<0) scrollMax = 0;
	
	if(scrollMax>0) {
		// Don't abridge when there's a scrollbar
		setAbridgedHeight(height);
		abridged = false;
	}
	
	scrollbarChanged(0, scrollMax, start);
}

void TerminalEmulator::setScroll(int where)
{
	term_scroll(terminal, 1, where);
}

void TerminalEmulator::setAbridgedHeight(int height)
{
	abridgedHeight = height;
	heightChanged();
}

RemoteJob *TerminalEmulator::getOwner()
{
	return owner;
}



